package cloudspace.stats;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.log4j.Logger;
import org.zkoss.zk.ui.Executions;

import cloudspace.config.CloudSpaceConfiguration;
import cloudspace.security.permissions.PermissionsManager;
import cloudspace.vm.VM;
import cloudspace.vm.filesystem.CSPath;


public class StatisticsService
{
    private static String prefix;
    static
    {
        prefix = CloudSpaceConfiguration.getInstance()
            .getStorageLocation()
            .getAbsolutePath()
            + "/data/achieve_";
    }

    private static StatisticsService singleton = new StatisticsService();

    protected Logger logger = Logger.getLogger( StatisticsService.class );

    public List<StatisticType> emptyAchievements = new ArrayList<StatisticType>();

    public Map<String, Map<String, StatisticType>> userToAchievementsMap = new HashMap<String, Map<String, StatisticType>>();

    public Map<String, Properties> userPersistance = new HashMap<String, Properties>();


    private StatisticsService()
    {
        init();
    }


    private void init()
    {
        Properties prop = new Properties();
        String configFile = "/configs/achievement.conf";
        try
        {
            File configuration = new File( Executions.getCurrent()
                .getDesktop()
                .getWebApp()
                .getRealPath( configFile ) );
            prop.load( new FileInputStream( configuration ) );
        }
        catch ( IOException e )
        {
            logger.error( "Could not open configuration file " + configFile );
            // System.err.println("Could not load achievement.conf");

        }
        for ( Object key : prop.keySet() )
        {

            String configs = (String)prop.get( key );
            String[] args = configs.split( "," );
            try
            {
                logger.trace( "Loading: " + args[4] );
                StatisticType achieve = (StatisticType)Class.forName( args[4] )
                    .newInstance();
                achieve.setup( (String)key, args[0], args[1], args[2], args[3] );
                emptyAchievements.add( achieve );
            }
            catch ( ClassNotFoundException e )
            {
                logger.error( "The tracker class is not valid" + args[4], e );
            }
            catch ( InstantiationException e )
            {
                logger.error( "Could not instantiate the class" + args[4], e );
            }
            catch ( IllegalAccessException e )
            {
                logger.error( "Could not access the constructor for " + args[4],
                    e );
            }
        }
    }


    public static StatisticsService getStatService()
    {
        return singleton;
    }


    public Map<String, StatisticType> getCurrentTracker()
    {
        String userName = getUserName();
        if ( userName == null )
            return null;
        return getTracker( userName );

    }


    private void initAchievements( String userName )
    {
        Properties prop = new Properties();
        File persistedInfo = new File( prefix + userName );
        if ( !persistedInfo.getParentFile().exists() )
        {
            persistedInfo.getParentFile().mkdir();
        }
        try
        {
            userPersistance.put( userName, prop );
            if ( !persistedInfo.exists() )
                persistedInfo.createNewFile();
            prop.load( new FileInputStream( persistedInfo ) );

        }
        catch ( FileNotFoundException e )
        {
            logger.error( "Persisted File does not exist "
                + persistedInfo.getAbsolutePath(),
                e );
        }
        catch ( IOException e )
        {
            logger.error( "IOException on " + persistedInfo.getAbsolutePath(),
                e );
        }
        userToAchievementsMap.put( userName,
            new HashMap<String, StatisticType>() );
        for ( StatisticType type : emptyAchievements )
        {
            logger.trace( "Adding " + type.getKey() + " to " + userName );
            StatisticType achieve = type.copy( userName, prop );
            if ( achieve != null )
                userToAchievementsMap.get( userName ).put( type.getKey(),
                    achieve );

        }

    }


    private Map<String, StatisticType> getTracker( String userName )
    {
        Map<String, StatisticType> tracker = userToAchievementsMap.get( userName );
        if ( tracker == null )
        {
            // tracker = new HashMap<String, StatisticType>();
            // userToAchievementsMap.put(userName, tracker);
            initAchievements( userName );
            tracker = userToAchievementsMap.get( userName );
        }
        return tracker;
    }


    private String getUserName()
    {
        // FIXME: This assumes one user per directory.
        String localRoot;
        try
        {
            String storageLoc;
            localRoot = ( new File( VM.lookupVM().getLocalRoot() ) ).getCanonicalPath();
            storageLoc = CloudSpaceConfiguration.getInstance()
                .getStorageLocation()
                .getCanonicalPath();
            if ( localRoot.startsWith( storageLoc ) )
            {
                localRoot = localRoot.substring( ( storageLoc + "/work" ).length() );
            }
        }
        catch ( IOException e )
        {
            logger.error( "Could not get Canonical Path of the VM's local root" );
            return null;
        }

        List<String> userNames = PermissionsManager.getInstance()
            .lookupUsers( new CSPath( "work", localRoot ) );
        if ( userNames == null || userNames.size() == 0)
        {
            return null;
        }
        return userNames.get( 0 );
    }


    public void observe( StatEvent observeEvent )
    {
        String key = observeEvent.getKey();
        logger.info( "Event " + key + "  was observed" );

        Map<String, StatisticType> tracker = getCurrentTracker();
        if ( tracker == null )
        {
            logger.info( "No tracker for this user." );
            return;
        }
        StatisticType achieve = tracker.get( key );
        if ( achieve != null )
        {
            logger.info( "Updating " + key );
            achieve.update( null, key );
            achieve.persist();
        }
    }


    public Set<String> getKeys( String userName )
    {
        Map<String, StatisticType> tracker = getTracker( userName );
        return tracker.keySet();
    }


    public Set<String> getUsers()
    {
        return userToAchievementsMap.keySet();
    }


    public StatisticType get( String username, String key )
    {
        Map<String, StatisticType> tracker = getTracker( username );
        if ( tracker == null )
            return null;

        return tracker.get( key );
    }


    public void persistAll()
    {
        for ( String userName : this.userPersistance.keySet() )
        {
            try
            {
                String fileName = prefix + userName;
                Properties prop = userPersistance.get( userName );
                prop.store( new FileOutputStream( fileName ), userName
                    + "'s persisted achievements" );
            }
            catch ( IOException e )
            {
                logger.error( "Could not persist " + userName
                    + "'s achievements", e );
            }
        }
    }
}
